﻿// <copyright>
// Copyright (c) 2008 - 2009, Microsoft, All Rights Reserved
// </copyright>
//
// Licensed under the Microsoft Public License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at 
//
// http://www.opensource.org/licenses/ms-pl.html

namespace LiveLabs.Logging.Util
{
    using System;
    using System.Diagnostics;

    /// <summary>
    /// Simple Clock abstraction to make unit testing deterministic
    /// </summary>
    internal static class Clock
    {
        private static Func<DateTime> nowProvider;

        private static Func<DateTime> utcNowProvider;

        /// <summary>
        /// Initializes static members of the Clock class.
        /// </summary>
        static Clock()
        {
            Reset();
        }

        /// <summary>
        /// Gets the current Local <see cref="System.DateTime"/>
        /// </summary>
        [DebuggerNonUserCode]
        internal static DateTime Now
        {
            get { return nowProvider(); }
        }

        /// <summary>
        /// Gets the current UTC <see cref="System.DateTime"/>
        /// </summary>
        [DebuggerNonUserCode]
        internal static DateTime UtcNow
        {
            get { return utcNowProvider(); }
        }

        /// <summary>
        /// Resets the clock to use <see cref="System.DateTime.Now"/> and <see cref="System.DateTime.UtcNow"/>
        /// </summary>
        [DebuggerNonUserCode]
        internal static void Reset()
        {
            SetNowProvider(() => DateTime.Now, false);
            SetUtcNowProvider(() => DateTime.UtcNow, false);
        }

        /// <summary>
        /// Sets the Clock to always return <paramref name="now"/> when <see cref="Clock.Now"/> called.
        /// </summary>
        /// <param name="now">The Local <see cref="System.DateTime"/> to return</param>
        /// <remarks>
        /// If <paramref name="now"/> is not <see cref="DateTimeKind.Local"/>, it will
        /// be converted to <see cref="DateTimeKind.Local"/>.
        /// </remarks>
        [DebuggerNonUserCode]
        internal static void SetNow(DateTime now)
        {
            now = MakeLocal(now);
            SetNowProvider(() => now, false);
        }

        /// <summary>
        /// Sets the Clock to always return <paramref name="now"/> when <see cref="Clock.UtcNow"/> called.
        /// </summary>
        /// <param name="now">The UTC <see cref="System.DateTime"/> to return</param>
        /// <remarks>
        /// If <paramref name="now"/> is not <see cref="DateTimeKind.Utc"/>, it will
        /// be converted to <see cref="DateTimeKind.Utc"/>.
        /// </remarks>
        [DebuggerNonUserCode]
        internal static void SetUtcNow(DateTime now)
        {
            now = MakeUtc(now);
            SetUtcNowProvider(() => now, false);
        }

        /// <summary>
        /// Sets the Clock to use the <paramref name="nowProvider"/> when <see cref="Clock.Now"/> is called.
        /// </summary>
        /// <param name="nowProvider">The function to call to get a DateTime</param>
        [DebuggerNonUserCode]
        internal static void SetNowProvider(Func<DateTime> nowProvider)
        {
            if (nowProvider == null)
            {
                throw new ArgumentNullException("nowProvider");
            }

            SetNowProvider(nowProvider, true);
        }

        /// <summary>
        /// Sets the Clock to use the <paramref name="utcNowProvider"/> when <see cref="Clock.UtcNow"/> is called.
        /// </summary>
        /// <param name="utcNowProvider">The function to call to get a DateTime</param>
        [DebuggerNonUserCode]
        internal static void SetUtcNowProvider(Func<DateTime> utcNowProvider)
        {
            if (utcNowProvider == null)
            {
                throw new ArgumentNullException("utcNowProvider");
            }

            SetUtcNowProvider(utcNowProvider, true);
        }

        private static void SetNowProvider(Func<DateTime> provider, bool wrap)
        {
            if (wrap)
            {
                nowProvider = () => MakeLocal(provider());
            }
            else
            {
                nowProvider = provider;
            }
        }

        private static void SetUtcNowProvider(Func<DateTime> provider, bool wrap)
        {
            if (wrap)
            {
                utcNowProvider = () => MakeUtc(provider());
            }
            else
            {
                utcNowProvider = provider;
            }
        }

        private static DateTime MakeLocal(DateTime dt)
        {
            switch (dt.Kind)
            {
            case DateTimeKind.Unspecified:
                dt = DateTime.SpecifyKind(dt, DateTimeKind.Local);
                break;

            case DateTimeKind.Utc:
                dt = dt.ToLocalTime();
                break;
            }

            return dt;
        }

        private static DateTime MakeUtc(DateTime dt)
        {
            switch (dt.Kind)
            {
            case DateTimeKind.Unspecified:
                dt = DateTime.SpecifyKind(dt, DateTimeKind.Utc);
                break;

            case DateTimeKind.Local:
                dt = dt.ToUniversalTime();
                break;
            }

            return dt;
        }
    }
}
